package org.weasel.controllers;

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

import org.eclipse.swt.widgets.Composite;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleInsets;
import org.weasel.business.Iteration;
import org.weasel.business.Project;
import org.weasel.business.Release;
import org.weasel.business.UserStory;
import org.weasel.views.BurndownChartComposite;

/**
 * This class is the controller of the DeveloperComposite.java which represents a composite
 * to move users from one list to another. Depending to which controller this class here is attached, the moving
 * will have effect between company<->project or between project<->userstory developers.
 *
 */
public class BurndownChartController {
	private ProjectViewController parentController;
	private BurndownChartComposite burndownChartController; //holding the composite with the two dev lists
	private SimpleDateFormat bartDateFormat;

	public BurndownChartController(){
		this.bartDateFormat = new SimpleDateFormat("dd.MM.yyyy");
	}
	
	/**
	 * If attaching it to the projectDetailView
	 * @param parentController
	 */
	public void setProjectViewController(ProjectViewController parentController){
		this.parentController = parentController;
	}
	
	
	public void refreshComposite(){
		writeChartToFile();
		burndownChartController.reload();
	}
	
	/**
	 * Both parameters are passed by the viewpart (over the controller)
	 * where the composite should be displayed
	 * @param parent
	 * @param style
	 */
	public BurndownChartComposite loadBurndownComposite(Composite parent, int style){
		burndownChartController = new BurndownChartComposite(parent,style, this);
		return burndownChartController;
	}
	
	/**
	 * Writes the created chart to the file
	 *
	 */
	public void writeChartToFile(){
		
		JFreeChart chart = createChart(); //make the chart
		try{
			File f=new File("burndownChart.jpg");
			ChartUtilities.saveChartAsJPEG(f, chart, 500, 300);
		}catch (java.io.IOException ex){
			ex.printStackTrace();
		}
	}
	
	/**
	 * Creates the overall structure of the chart
	 * @return
	 */
	private JFreeChart createChart() {
		XYDataset dataset = null;
		try {
			dataset = createDataset();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        JFreeChart chart = ChartFactory.createTimeSeriesChart(
                "User-stories Burn-down chart",  // title
                "Date",             // x-axis label
                "open User-stories",   // y-axis label
                dataset,            // data
                true,               // create legend?
                true,               // generate tooltips?
                false               // generate URLs?
            );

		XYPlot plot = (XYPlot) chart.getPlot();
		plot.setForegroundAlpha(0.5f);
		plot.setNoDataMessage("No data to display. Poss. reasons: Project start date not set; first iteration-end not yet reached (if iteration detailness)");
        plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
        plot.setDomainCrosshairVisible(true);
        plot.setRangeCrosshairVisible(true);
        
        XYItemRenderer r = plot.getRenderer();
        if (r instanceof XYLineAndShapeRenderer) {
            XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
            //check if detailness per day; if it is, don't display rectangles because they are too much
            if(PreferenceController.instance().getPrefStore().getBoolean(PreferenceController.BURNDOWNDETAIL_PREFERENCE))
            	renderer.setDefaultShapesVisible(false);
            else
            	renderer.setDefaultShapesVisible(true);
            renderer.setDefaultShapesFilled(false);
        }
        
        DateAxis axis = (DateAxis) plot.getDomainAxis();
        axis.setDateFormatOverride(new SimpleDateFormat("dd-MMM-yyyy"));
		return chart;
	}
	
	private XYDataset createDataset() throws Exception { 
		Date projectStartDate = null;
		Date projectEndDate = null;
		Date now = new Date();
	       
		//retrieve the project
		Project p = parentController.getProject();
		if(p.getStartDate()==null || p.getStartDate().equals("") || p.getEndDate()==null || p.getEndDate().equals("")){
			throw new Exception("Project start date not set or not computed!");
		}
		projectStartDate = bartDateFormat.parse(p.getStartDate());
		projectEndDate = bartDateFormat.parse(p.getEndDate());
		
		//collect all userstories
		ArrayList<UserStory> allStories = new ArrayList<UserStory>();
		collectUserStories(p, allStories);
		
		ArrayList<Iteration> allIterations = new ArrayList<Iteration>();
		for (Release r : p.getReleases()) {
			allIterations.addAll(r.getIterations());
		}
		
    	//bring all the userstories dates inside the project's timeframe
    	adjustStoryDates(projectStartDate, projectEndDate, allStories);
		
		TimeSeries s1 = null;
		
		if(PreferenceController.instance().getPrefStore().getBoolean(PreferenceController.BURNDOWNDETAIL_PREFERENCE)){
			s1 = new TimeSeries("Burndown line - Detail: per day", Day.class);
			createChartDataPerDay(projectStartDate, projectEndDate, now, allStories, s1);
		}else{
			s1 = new TimeSeries("Burndown line - Detail: per iteration", Day.class);
			createChartDataPerIteration(now, allStories, allIterations, s1);
		}
 
		TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(s1);

        dataset.setDomainIsPointsInTime(true);

        return dataset;
	}
	
	/**
	 * Creates the burn-down chart finegradet to each day, so for each day
	 * the open user-stories are checked and displayed
	 * @param bartDateFormat
	 * @param projectStartDate
	 * @param projectEndDate
	 * @param now
	 * @param allStories
	 * @param s1
	 * @throws ParseException
	 */
	private void createChartDataPerDay(Date projectStartDate, Date projectEndDate, Date now, ArrayList<UserStory> allStories, TimeSeries s1) throws ParseException {
		Calendar cal = Calendar.getInstance();
		//setting the date to the current
		cal.setTime(projectStartDate);
		
		while((cal.getTime().before(projectEndDate) || cal.getTime().equals(projectEndDate)) && cal.getTime().before(now)){
			Date day = cal.getTime();
			int openStories = countOpenStoriesTillDate(allStories, day);
			s1.add(new Day(day), openStories);
			cal.add(Calendar.DATE, 1);
		}
	}

	/**
	 * Creates the burn-down chart finegradet to the iteration, so at each iteration-end all
	 * the open user-stories are checked
	 * @param bartDateFormat
	 * @param now
	 * @param allStories
	 * @param allIterations
	 * @param s1
	 * @throws ParseException
	 */
	private void createChartDataPerIteration(Date now, ArrayList<UserStory> allStories, ArrayList<Iteration> allIterations, TimeSeries s1) throws ParseException {
		for (Iteration iteration : allIterations) {
			//parse the iteration-end date as Date object
			Date iterBegin = bartDateFormat.parse(iteration.getStartDate());
			Date iterEnd = bartDateFormat.parse(iteration.getEndDate());
			
			if(iterEnd.before(now)){
				int openStories = 0;
				openStories = countOpenStoriesTillDate(allStories, iterEnd);
				s1.add(new Day(iterEnd), openStories);			
			}
		}
	}

	/**
	 * Counts all open userstories till the date indicated by the parameter "endDate"
	 * @param bartDateFormat
	 * @param allStories
	 * @param endDate
	 * @return
	 * @throws ParseException
	 */
	private int countOpenStoriesTillDate(ArrayList<UserStory> allStories, Date endDate) throws ParseException {
		int result = 0;
		for (UserStory story : allStories) {
			if(story.getGeneratedDate()!=null && !story.getGeneratedDate().equals("")){
				//parse the story creation date into a Date object
				Date genDate = bartDateFormat.parse(story.getGeneratedDate());
				
				Date finDate = null;
				if(story.getFinishedDate()!=null && !story.getFinishedDate().equals("")) 
					finDate = bartDateFormat.parse(story.getFinishedDate());
				if(genDate.before(endDate) || genDate.equals(endDate)){
					if(finDate==null || finDate.after(endDate))
						result++;
				}
			}
		}
		return result;
	}

	/**
	 * Adjusts the dates of the user stories such date creation-dates that are before the project
	 * start date are set to the project start date and also finished dates that are after the project end date
	 * are set to the project end date etc...
	 * @param bartDateFormat
	 * @param projectStartDate
	 * @param projectEndDate
	 * @param allStories
	 * @throws ParseException
	 */
	private void adjustStoryDates(Date projectStartDate, Date projectEndDate, ArrayList<UserStory> allStories) throws ParseException {
		for (UserStory story : allStories) {
    		if(story.getGeneratedDate()==null || story.getGeneratedDate().equals("")){
				//just set it to the start-date of the project
				story.setGeneratedDate(bartDateFormat.format(projectStartDate));
			}
			Date storyStartDate = bartDateFormat.parse(story.getGeneratedDate());
			Date storyFinishDate = null;
			if(story.getFinishedDate()!=null && !story.getFinishedDate().equals(""))
				storyFinishDate = bartDateFormat.parse(story.getFinishedDate());
			/*
			 * Do checks:
			 * 	a) if generated date is < projectStartDate --> set its date to the projectStartDate
			 *  b) if generated date is > projectEndDate --> (not very plausible) however set date to projectEndDate
			 */
			if(storyStartDate.before(projectStartDate)){
				story.setGeneratedDate(bartDateFormat.format(projectStartDate));
			}else if(storyStartDate.after(projectEndDate)){
				story.setGeneratedDate(bartDateFormat.format(projectEndDate));
			}
			if(storyFinishDate!=null)
				if(storyFinishDate.after(projectEndDate))
					story.setFinishedDate(bartDateFormat.format(projectEndDate));
		}
	}

	/**
	 * Collects all userstories of all iterations and unscheduled folders
	 * @param p
	 * @param allStories
	 */
	private void collectUserStories(Project p, ArrayList<UserStory> allStories) {
		allStories.addAll(p.getUnscheduled().getUserStories());
		for (Release r : p.getReleases()) {
			for (Iteration i : r.getIterations()) {
				allStories.addAll(i.getUserStories());
			}
		}
	}

	
}
